﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace SocketAsyncTest {
    public class PooledSocketAsync {

        static EventHandler<SocketAsyncEventArgs> sendToCompleted = new EventHandler<SocketAsyncEventArgs>(saea_SendToCompleted);
        static EventHandler<SocketAsyncEventArgs> receiveFromCompleted = new EventHandler<SocketAsyncEventArgs>(saea_ReceiveFromCompleted);
        //static string expectedss = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C01100020001000151800002C011C0110001000100015180000401020304";

        static string expectedss = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C00C00020001000151800002C011C0110001000100015180000401020304";
        static int TotalSendTo = 0;
        static int TotalReceiveFrom = 0;
        
        static SocketAsyncEventArgsPool SaeaPool;
        static Semaphore SemaphoreConnections;


        internal class MyUserToker {
            internal Socket Socket;
        }

        public void Test10000() {

            int maxConnection = 500;
            PooledSocketAsync.SaeaPool = new SocketAsyncEventArgsPool(maxConnection);
            PooledSocketAsync.SemaphoreConnections = new Semaphore(maxConnection, maxConnection);
            PooledSocketAsync.InitSaeaPool(maxConnection);

            int numSentTo = 100000;
            for (int i = 0; i < numSentTo; i++) {
                TestSocketClient();
                Console.Out.Flush();
            }

            Console.WriteLine("Wait a few seconds");
            Thread.Sleep(15000);

            Console.WriteLine();
            Console.WriteLine("Saea in pool: {0}", PooledSocketAsync.SaeaPool.Count);
            Console.WriteLine("Missing saea: {0}", maxConnection - PooledSocketAsync.SaeaPool.Count);
            Console.WriteLine("Total sent: {0}", PooledSocketAsync.TotalSendTo);
            Console.WriteLine("Total received: {0}", PooledSocketAsync.TotalReceiveFrom);

        }

        private static void InitSaeaPool(int maxConnection) {

            // Preallocate pool of SocketAsyncEventArgs objects.
            for (int i = 0; i < maxConnection; i++) {
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                SaeaPool.Push(e);
            }

        }


        public void TestSocketClient() {

            Byte[] buffer = new Byte[1024];

            string sendss = "00030100000100000000000004686F737406646F6D61696E03746C640000010001";
            byte[] sends = StringToByteArray(sendss);

            Array.Copy(sends, 0, buffer, 0, sends.Length);
            EndPoint endpoint = new IPEndPoint(IPAddress.Parse("192.168.168.2"), 53);
            MyUserToker mut = new MyUserToker();

            PooledSocketAsync.SemaphoreConnections.WaitOne();
            SocketAsyncEventArgs e = PooledSocketAsync.SaeaPool.Pop();

            e.SetBuffer(buffer, 0, sends.Length);
            e.RemoteEndPoint = endpoint;
            e.Completed += sendToCompleted;
            e.UserToken = mut;

            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            mut.Socket = socket;

            bool iopending = socket.SendToAsync(e);
            if (iopending == false) {
                saea_SendToCompleted(this, e);
            }

        }

        static void saea_SendToCompleted(object sender, SocketAsyncEventArgs e) {

            Interlocked.Increment(ref PooledSocketAsync.TotalSendTo);
            Console.Write(">", PooledSocketAsync.TotalSendTo);
            if (PooledSocketAsync.TotalSendTo % 80 == 0) { Console.WriteLine(); }

            if (e.LastOperation != SocketAsyncOperation.SendTo) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }
            if (e.SocketError != SocketError.Success) {
                throw new SocketException((int)e.SocketError);
            }
            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }
            MyUserToker mut = e.UserToken as MyUserToker;
            if (mut.Socket == null) {
                throw new ArgumentNullException("Socket");
            }

            e.SetBuffer(e.Offset + e.BytesTransferred, 512);
            e.Completed -= sendToCompleted;
            e.Completed += receiveFromCompleted;

            bool iopending = mut.Socket.ReceiveFromAsync(e);
            if (iopending == false) {
                saea_ReceiveFromCompleted(sender, e);
            }

        }

        static void saea_ReceiveFromCompleted(object sender, SocketAsyncEventArgs e) {

            Interlocked.Increment(ref PooledSocketAsync.TotalReceiveFrom);
            Console.Write("<", PooledSocketAsync.TotalReceiveFrom);
            if (PooledSocketAsync.TotalReceiveFrom % 80 == 0) { Console.WriteLine(); }

            if (e.LastOperation != SocketAsyncOperation.ReceiveFrom) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }
            if (e.SocketError != SocketError.Success) {
                throw new SocketException((int)e.SocketError);
            }
            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }
            MyUserToker mut = e.UserToken as MyUserToker;
            if (mut.Socket == null) {
                throw new ArgumentNullException("Socket");
            }

            e.Completed -= receiveFromCompleted;
            mut.Socket.Close();

            string receivedss = ByteArrayToString(e.Buffer, e.Offset, e.BytesTransferred);
            if (receivedss != PooledSocketAsync.expectedss) {
                Console.WriteLine();
                Console.WriteLine(receivedss);
            } else {
                Console.Write("+");
            }

            PooledSocketAsync.SaeaPool.Push(e);
            PooledSocketAsync.SemaphoreConnections.Release();


        }


        public static byte[] StringToByteArray(string s) {

            string hex = s.Replace(" ", "");

            int NumberChars = hex.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;

        }

        public static string ByteArrayToString(byte[] ba, int offset, int count) {
            string hex = BitConverter.ToString(ba, offset, count);
            return hex.Replace("-", "");
        }

    }
}
